package me.itsoo.artemis.framework.core.util;

import lombok.AccessLevel;
import lombok.NoArgsConstructor;

import java.lang.annotation.Annotation;
import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.HashSet;
import java.util.Set;

/**
 * AnnotationUtils
 *
 * @author zxy
 * @version 0.0.1
 * @since 2023/5/8
 */
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class AnnotationUtils {

    /**
     * 字段中存在注解
     *
     * @param field    Field
     * @param annClass Class
     * @param <T>      T
     * @return boolean
     */
    public static <T extends Annotation> boolean hasAnnotation(Field field, Class<T> annClass) {
        final T ann = findAnnotation((AnnotatedElement) field, annClass);
        return (ann != null);
    }

    /**
     * 方法中存在注解
     *
     * @param method   Method
     * @param annClass Class
     * @param <T>      T
     * @return boolean
     */
    public static <T extends Annotation> boolean hasAnnotation(Method method, Class<T> annClass) {
        final T ann = findAnnotation((AnnotatedElement) method, annClass);
        return (ann != null);
    }

    /**
     * 类型中存在注解
     *
     * @param clazz    Class
     * @param annClass Class
     * @param <T>      T
     * @return boolean
     */
    public static <T extends Annotation> boolean hasAnnotation(Class<?> clazz, Class<T> annClass) {
        final T ann = findAnnotation(clazz, annClass, new HashSet<>());
        return (ann != null);
    }

    /**
     * 可标注类型中存在注解
     *
     * @param annElement AnnotatedElement
     * @param annClass   Class
     * @param <T>        T
     * @return boolean
     */
    public static <T extends Annotation> boolean hasAnnotation(AnnotatedElement annElement, Class<T> annClass) {
        final T ann = findAnnotation(annElement, annClass);
        return (ann != null);
    }

    /**
     * 从字段中寻找注解
     *
     * @param field    Field
     * @param annClass Class
     * @param <T>      T
     * @return T
     */
    public static <T extends Annotation> T findAnnotation(Field field, Class<T> annClass) {
        return findAnnotation((AnnotatedElement) field, annClass);
    }

    /**
     * 从方法中寻找注解
     *
     * @param method   Method
     * @param annClass Class
     * @param <T>      T
     * @return T
     */
    public static <T extends Annotation> T findAnnotation(Method method, Class<T> annClass) {
        return findAnnotation((AnnotatedElement) method, annClass);
    }

    /**
     * 从类型中寻找注解
     *
     * @param clazz    Class
     * @param annClass Class
     * @param <T>      T
     * @return T
     */
    public static <T extends Annotation> T findAnnotation(Class<?> clazz, Class<T> annClass) {
        return findAnnotation(clazz, annClass, new HashSet<>());
    }

    /**
     * 从类型中寻找注解
     *
     * @param clazz    Class
     * @param annClass Class
     * @param visited  Set
     * @param <T>      T
     * @return T
     */
    private static <T extends Annotation> T findAnnotation(Class<?> clazz, Class<T> annClass, Set<Annotation> visited) {
        try {
            T ann = clazz.getDeclaredAnnotation(annClass);
            if (ann != null) {
                return ann;
            }

            final Annotation[] innerAnnotations = clazz.getDeclaredAnnotations();
            for (Annotation innerAnn : innerAnnotations) {
                final Class<? extends Annotation> innerType = innerAnn.annotationType();
                if (!isInJavaLangAnnotationPackage(innerType) && visited.add(innerAnn)) {
                    ann = findAnnotation(innerType, annClass, visited);
                    if (ann != null) {
                        return ann;
                    }
                }
            }
        } catch (Exception e) {
            return null;
        }

        for (Class<?> ifc : clazz.getInterfaces()) {
            final T ann = findAnnotation(ifc, annClass, visited);
            if (ann != null) {
                return ann;
            }
        }

        final Class<?> superClass = clazz.getSuperclass();
        if (superClass == null || Object.class == superClass) {
            return null;
        }

        return findAnnotation(superClass, annClass, visited);
    }

    /**
     * 从可标注类型中寻找注解
     *
     * @param annElement AnnotatedElement
     * @param annClass   Class
     * @param <T>        T
     * @return T
     */
    public static <T extends Annotation> T findAnnotation(AnnotatedElement annElement, Class<T> annClass) {
        try {
            T ann = annElement.getAnnotation(annClass);
            if (ann != null) {
                return ann;
            }

            final Annotation[] innerAnnotations = annElement.getAnnotations();
            final Set<Annotation> visited = new HashSet<>();
            for (Annotation innerAnn : innerAnnotations) {
                final Class<? extends Annotation> innerType = innerAnn.annotationType();
                ann = findAnnotation(innerType, annClass, visited);
                if (ann != null) {
                    return ann;
                }
            }
        } catch (Exception e) {
            return null;
        }

        return null;
    }

    /**
     * 判断是否为 java 包注解
     *
     * @param ann Annotation
     * @return boolean
     */
    public static boolean isInJavaLangAnnotationPackage(Annotation ann) {
        return (ann != null) && isInJavaLangAnnotationPackage(ann.annotationType());
    }

    /**
     * 判断是否为 java 包注解
     *
     * @param ann Class
     * @return boolean
     */
    public static boolean isInJavaLangAnnotationPackage(Class<? extends Annotation> ann) {
        return (ann != null) && isInJavaLangAnnotationPackage(ann.getName());
    }

    /**
     * 判断是否为 java 包注解
     *
     * @param annTypeName String
     * @return boolean
     */
    public static boolean isInJavaLangAnnotationPackage(String annTypeName) {
        return (annTypeName != null) && annTypeName.startsWith("java.lang.annotation");
    }
}
